package spring.mybatis.mapperScan;

import com.me.study.javaCore.enumm.BaseEnum;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.ImportResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import spring.mybatis.domain.EnumValueHandler;
import spring.mybatis.mapper.UserMapper;
import spring.mybatis.service.UserService;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * mybatis-spring 通过 MapperScannerConfigurer 批量生成 Mapper接口
 *
 * @author ME
 * @date 2021/5/20
 */
public class TestMybatisMapperScan {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MapperScanConfig.class);

        UserService service = context.getBean(UserService.class);
        System.out.println("UserService: " + service.getUser(3));
        System.out.println();

        UserMapper mapper = context.getBean(UserMapper.class);
        System.out.println("UserMapper: " + mapper.getUser(1));
    }

    /** 配置类 */
    @Configuration
    @MapperScan("spring.mybatis.mapper")
    @ImportResource("classpath:spring/mybatis/mapperScan/spring-scanner.xml")
    @EnableAspectJAutoProxy(proxyTargetClass = true)// 强制使用 Cglib动态代理
    public static class MapperScanConfig {

        private static final Logger LOG = org.slf4j.LoggerFactory.getLogger(MapperScanConfig.class);

        /**
         * 1，springboot 自动配置：MybatisAutoConfiguration#applyConfiguration 中暴露了 ConfigurationCustomizer，
         *  允许提前配置 Configuration，在 ConfigurationCustomizer 中扫描包自动注册。
         *      ！！！但是，
         *      --- 使用 SqlSessionFactoryBean 配置的方式不能这么做，SqlSessionFactoryBean 实现了 InitializingBean，
         *              初始化之后立即调用 afterPropertiesSet() 解析 xml 并使用。
         *              这种情况下，虽然修改操作完成了，但是 SqlSessionFactory 使用的是 旧的Configuration。
         *      --- 直接调用 SqlSessionFactoryBean.getObject() 也会调用 afterPropertiesSet() 解析xml，并生成 SqlSessionFactory
         * 2，SpringBoot javaConfig 手动配置 SqlSessionFactory 时，直接获取 Configuration 扫包配置。
         *      `sqlSessionFactory.getConfiguration().getTypeHandlerRegistry().register(ColorEnum.class, EnumValueHandler.class)`
         * 3，xml 配置 SqlSessionFactoryBean：只能在 configLocation 指向的 xml中 手动注册
         */
        @Bean
        public BeanPostProcessor mybatisTypeHandlerBeanPostProcessor(@Value("${mybatisConfig.main.typeHandlersPackage:''}") String typeHandlersPackage) {
            typeHandlersPackage = "spring.mybatis.mapperScan,spring.mybatis.mapperScan1";
            return new MybatisTypeHandlerBeanPostProcessor(typeHandlersPackage);
        }







        @Value("${mybatisConfig.main.typeHandlersPackage}")
        private String typeHandlersPackage;
        /**
         * 批量注册 TypeHandler
         */
        public void registerTypeHandler(org.apache.ibatis.session.Configuration configuration) throws BeansException {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

            Set<Class<?>> classes = scanClasses(typeHandlersPackage);
            classes.forEach(cls -> {
                typeHandlerRegistry.register(cls, EnumValueHandler.class);
            });
        }

        /**
         * 扫描 BaseEnum 类型的 枚举类
         *
         * @param packagePatterns   需要扫描的 TypeHandler 目录
         * @return                  指定路径下所有实现了 BaseEnum接口 的 枚举类
         */
        private Set<Class<?>> scanClasses(String packagePatterns) {
            String[] packagePatternArray = StringUtils.tokenizeToStringArray(packagePatterns, ",; ");
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Set<Class<?>> classes = new HashSet<>();

            for (String packagePattern : packagePatternArray) {
                try {
                    Resource[] resources = resolver.getResources("classpath*:" + ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
                    MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resolver);
                    for (Resource resource : resources) {
                        ClassMetadata classMetadata = readerFactory.getMetadataReader(resource).getClassMetadata();
                        Class<?> clazz = Class.forName(classMetadata.getClassName());
                        if (!clazz.isInterface() && BaseEnum.class.isAssignableFrom(clazz)) {
                            if (clazz.isEnum()) {
                                classes.add(clazz);
                            }
                            else {
                                LOG.warn("该 BaseEnum 实现不是枚举类型: {}", clazz.getName());
                            }
                        }
                    }
                } catch (IOException | ClassNotFoundException e) {
                    LOG.error("类加载失败: ", e);
                }
            }

            return classes;
        }

    }

}
